/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package unused;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.HashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author loskov
 */
public class Factorial {

    private final Log log = LogFactory.getLog(this.getClass());

    private static final MathContext MATH_CONTEXT = new MathContext(16, RoundingMode.CEILING);
    private static final HashMap<Integer, BigDecimal> factorialDecimalCacheMap = new HashMap<>();
    private static final HashMap<Integer, BigDecimal> factorialLogCacheMap = new HashMap<>();
    private static final HashMap<String, BigDecimal> pochhammerCacheMap = new HashMap<>();

    public static enum FactorialType {

        DECIMAL, LOG, POCHHAMMER
    }

    private BigDecimal result;
    private FactorialType type;

    public Factorial() {

    }

    @Override
    public String toString() {
        return type + " Factorial {" + "result=" + result + '}';
    }

    public BigDecimal factLogCached(Integer n) {
        if (factorialLogCacheMap.containsKey(n)) {
            return factorialLogCacheMap.get(n);
        } else {
            result = new BigDecimal(0);
            for (int i = 1; i <= n; i++) {
                result = result.add(new BigDecimal(Math.log(i)));
                factorialLogCacheMap.put(i, result);
            }
            type = FactorialType.LOG;
            return result;
        }

    }

    public BigDecimal factDecimalCached(Integer n) {
        type = FactorialType.DECIMAL;
        if (factorialDecimalCacheMap.containsKey(n)) {
            return factorialDecimalCacheMap.get(n);
        } else {
            if (n.intValue() < 0) {
                return null;
            } else if (n == 0 || n == 1) {
                factorialDecimalCacheMap.put(n, BigDecimal.ONE);
                return BigDecimal.ONE;
            } else if (n.intValue() < 200) {
                BigDecimal nDec = new BigDecimal(n);
                BigDecimal result = nDec.multiply(factDecimalCached(n - 1));
                factorialDecimalCacheMap.put(n, result);
                return result;
            } else {
                // Stirling's formula: n! ~ (n/e)^n * sqrt(2*pi*n)
                // for n > 200 error ~ 0,04 %
                BigDecimal nDec = new BigDecimal(n);
                BigDecimal eDec = new BigDecimal(Math.E);
                BigDecimal nDivE = nDec.divide(eDec, MATH_CONTEXT);
                BigDecimal powerPart = nDivE.pow(n.intValue());
                BigDecimal sqrtPart = new BigDecimal(Math.sqrt(2 * Math.PI * n.doubleValue()));
                BigDecimal result = powerPart.multiply(sqrtPart);
                factorialDecimalCacheMap.put(n, result);
                return result;
            }
        }
    }

    public BigDecimal pochhammerFallFact(Integer n, Integer k) {
        type = FactorialType.POCHHAMMER;
        String pochFactId = n + "," + k;
        if (pochhammerCacheMap.containsKey(pochFactId)) {
            return pochhammerCacheMap.get(pochFactId);
        } else {
            BigDecimal pochFact = new BigDecimal(1);
            BigDecimal xDec = new BigDecimal(n);
            BigDecimal step;

            for (int i = 1; i <= k; i++) {
                step = xDec.add(new BigDecimal(1 - i));
                pochFact = pochFact.multiply(step);
            }

            pochhammerCacheMap.put(pochFactId, pochFact);
            return pochFact;
        }

    }

}
